/**
 * Use Validator to see if a Javscript Object, String, Number or Boolean matches a required configuration or value.
 *
 *	To use a Validator, first construct it with the configuration or value, then pass the item you wish to validate to
 *	it's 'valid' method.
 *
 *	A Validator is constructed with up to two arguments.
 *	*	The first argument is always required, and is the value or configuration you are matching against,
 *		It may be a string, number, null value, boolean, function, regular expression, or object
 *		If the first argument is a string, number, null value, or boolean the second argument is required if the Validator
 *		is to do meaninful work.
 *
 *		If the first argument is a function, regular expression, or object the second argument is not used.
 *		If the first argument is a regular expression calls to valid will will be text matched against the regular expresion.
 *		If the first argument is a function, that function will be given the value passed to valid.
 *		If the first argument is an object, see docs after  second argument description
 *
 *	*	The second argument is a match operation list. There are 3 main types of operators
 *		Comparison: ==, ===, !=, !==, >, <  (not used if regex, object, or function)
 *		Typeof: type= (not used if regex, object, or function)
 *		Presence: optional, notexist, (if omitted assumed 'required')
 *
 *		Comparison operators will perform an arithmetic style operation on the value passed to 'valid', using the 1st
 *		argument as the right hand component of the expression.
 *		Typeof operators will perform a 'typeof' call and match against the end of the 'typeof=' string ex:'typeof=boolean'
 *		Presence operators are covered in the next section.
 *
 *		One of each type of operator may be present, separated by ';' ex: 'type=number;>'
 *
 *	*	Objects passed to constructor.
 *		An object passed to the constructor describes a validation for each of it's properties. Each property is formatted
 *		as a match operation list (second argument) with the actual property name as the first item in the list, followed
 *		by the match operations you wish performed.
 *		ex: a numeric value called 'actual' that should be greater than five.
 *			{'actual;>':5}
 *		ex: a property 'fiz' that is optional, but must be nothing but the letter 'b'
 *		   {'fiz;optional':/^b+$/}
 *
 *
 */
(function($, document) {
    function Response(ok, data, msg) {
        this.allGood = ok;
        this.data = data;
        this.message = (msg)? msg : '';
    }

    Response.prototype.ok = function() {
        return this.allGood;
    }

    function Validator(cfg, conditions, parent) {
        var cond, i, validator;
        this.attrName =
        this.comparator =
        this.notexist =
        this.optional =
        this.type = false;
        this.value = null;

        conditions = (conditions)? conditions : [];

        if(!conditions.constructor == Array) {
            conditions = conditions.split(';');
        }

        if(parent) {
            this.attrName = $.trim(conditions.shift());
        }

        while(cond = conditions.shift()) {
            cond = $.trim(cond);
            switch(cond) {
                case '==' :
                case '===' :
                case '!=' :
                case '!==' :
                case '>' :
                case '<' :
                case '=' :
                    this.comparator = cond;
                    break;
                case 'notexist' :
                    this.notexist = true;
                    break;
                case 'optional' :
                    this.optional = true;
                    break;
                default :
                    if(cond.indexOf('type=') === 0) {
                        this.type = cond.replace('type=', '');
                    }
            }

        }

        switch(typeof cfg) {
            case 'object' :
                if(cfg == null) {
                } else if(cfg.constructor == Object) {
                    this.attributes = {};
                    for(var i in cfg) if(cfg.hasOwnProperty(i)) {
                        validator = new Validator(cfg[i], i, this)
                        this.attributes[validator.attrName] = validator;
                    }
                    this.valid = this.validByObj;
                } else if (cfg.constructor == RegExp) {
                    this.regEx = cfg;
                    this.valid = this.validByRegEx;
                } else {
                    throw Exception();
                }
                break;

            case 'function' :
                this.valid = cfg;
                break;
            case 'boolean' :
            default :
                this.value = cfg;

        }
    }

    /**
     * returns the validator for the passed in object path
     */
    Validator.prototype.getValidator = function(attrPath) {
        var attr;
        attrPath = (attrPath.constructor == Array)? attrPath :  attrPath.split('.');

        if(attrPath.length == 0) {
            return this;
        }

        attr = attrPath.shift();
        return (this.attributes.hasOwnProperty(attr))? this.attributes.getValidator(attrPath) : false;
    }


    Validator.prototype.validByObj = function(obj, runAll) {
        var attr;
        for(attr in this.attributes) if(this.attributes.hasOwnProperty(attr)) {
            if(!obj.hasOwnProperty(attr)) {
                if(this.attributes[attr].optional == false && this.attributes[attr].notexist == false) {
                    return false;
                }
            } else if(this.attributes[attr].notexist) {
                return false
            } else {
                if(!this.attributes[attr].valid(obj[attr])) {
                    return false;
                }
            }

        }
        return true;
    }


    Validator.prototype.valid = function(value) {
        var val = (value.constructor == Array)? value.length : value;
        var valtype = typeof value;
        if(this.type) {
            if(!this.type == valtype) {
                return false;
            }
        }
        if(this.comparator) {
            switch(this.comparator) {
                case '==' :
                    if(!(this.value == val)) {
                        return false;
                    }
                    break;
                case '===' :
                    if(!(this.value === value)) {
                        return false;
                    }
                    break;
                case '!==' :
                    if(!(this.value !== value)) {
                        return false;
                    }
                    break;
                case '!=' :
                    if(!(this.value != val)) {
                        return false;
                    }
                    break;
                case '>' :
                    if(!(this.value > val)) {
                        return false;
                    }
                    break;
                case '<' :
                    if(!(this.value < val)) {
                        return false;
                    }
                    break;
            }
        }
        return true;
    }


    Validator.prototype.validByRegEx = function(e) {
        return this.regEx.text(e);
    }

    return Validator;

})(jQuery, document);